<?php
/**
 * Class for returning formatted references from QuikBib
 *
 * @copyright 2009 The Open University
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

class quikbib
{
    public static $proxy = '137.108.192.28';
    public static $proxyport = 80;
    public static $vendor = '?';
    public static $secretcode = '?';
    public static $tagused = 'FormattedOutput';
    public static $tagindicator = 'style="text-indent';
    public static $carriagereturn = "\n";
    public static $quikbiburl = 'http://www.quikbib.com/?';
    //set to true to sort the output array to match input order
    public static $sortbyinput = false;
    //error text from the last error
    public static $lasterror = '';

    /*
     * Array of styles available to select when updating module
     * Order will be matched in options form - so 1 will be first array element
     * String - get_string identifier for style
     * quikbib - name of style that will be used if using quikbib
     */
    public static $referencestyles=array(
    array('string'=>'ouharvard','quikbib'=>'OU Harvard'),
    //array('string'=>'ouharvardhsc','quikbib'=>'OU Harvard HSC')
    );

    /**
     * This function receives at minimum a string containing at least one refernce in RIS format
     * with options to receive a style type. The option to deal with an alternative string format
     * has not yet been developed. The reference(s) is/are sent to QuikBib via CURL and received back
     * and manipulated into an array which is returned
     *
     * @param string $references
     * @param string $style
     * @param string $filter
     * @return array $returnarray
     */
    static public function get_formatted_references ($references, $style = 'Harvard%20-%20British%20Standard',$filter = 'RIS%20Format&database=RIS%20Format%20UTF-8') {
        //creation of QuikBib authorisation string
        self::$lasterror='';
        $sagent = $_SERVER['HTTP_USER_AGENT'];
        $tempstring = strtolower(self::$secretcode.self::$vendor.$sagent);
        $tempstring2 = base64_encode(sha1($tempstring,true));
        $authID = rawurlencode($tempstring2);

        $url = self::$quikbiburl; //url supplied by QuikBib
        $qstring= 'vendor='.self::$vendor.'&filter='.$filter.'&debug=off&style='.$style.'&format=0&servicecall=true&authID='.$authID;
        $url.=$qstring;
        $c = curl_init($url);
        curl_setopt($c, CURLOPT_POST, true);

        // encoding of string to submit to QuikBib
        if (mb_detect_encoding($references, "UTF-8, ASCII, ISO-8859-1")!="UTF-8") {
            $references=utf8_encode($references);
        }
        //$references = mb_convert_encoding($references, 'HTML-ENTITIES', 'UTF-8');

        curl_setopt($c, CURLOPT_POSTFIELDS, $references);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($c, CURLOPT_USERAGENT, $sagent);
        curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 15);

        //manual header
        $header[] = "Content-type: text/html";
        curl_setopt($c, CURLOPT_HTTPHEADER, $header);

        //OU proxy
        curl_setopt($c, CURLOPT_PROXY, self::$proxy);
        curl_setopt($c, CURLOPT_PROXYPORT, self::$proxyport);
        curl_setopt($c, CURLOPT_HTTPPROXYTUNNEL, false);
        $returnedarray = array(); // for results
        $page = curl_exec($c);
        if ($page == false) {
            self::$lasterror = curl_getinfo($c,CURLINFO_HTTP_CODE);
            return array();
        }else{
            if(strpos($page,'<?xml')===false){
                self::$lasterror = 'Unexpected message format';
                return false;
            }
        }
        curl_close ($c);

        //build from xml
        $xml = new DOMDocument('1.0','utf-8');
        $xml->loadXML($page);
        if($xml->getElementsByTagName('ResultMessage')->item(0)->nodeValue!='success'){
            self::$lasterror=$xml->getElementsByTagName('ResultMessage')->item(0)->nodeValue;
            return array();
        }

        $referencesstring=$xml->getElementsByTagName(self::$tagused)->item(0)->nodeValue;
        unset($xml);
        //string manipulation to add "proper" html tags
        $referencesstring=str_ireplace('<B>','<span style=\'font-weight:bold\'>',$referencesstring);
        $referencesstring=str_ireplace('</B>','</span>',$referencesstring);
        $referencesstring=str_ireplace('<I>','<span style=\'font-style:italic\'>',$referencesstring);
        $referencesstring=str_ireplace('</I>','</span>',$referencesstring);
        $referencesstring=str_ireplace('<U>','<span style=\'text-decoration:underline\'>',$referencesstring);
        $referencesstring=str_ireplace('</U>','</span>',$referencesstring);
        $referencesstring=strip_tags($referencesstring,'<span>');
        //decode any html entities as we use utf-8
        $referencesstring = str_replace("&amp;", "&", $referencesstring);
        $referencesstring = (html_entity_decode($referencesstring, ENT_QUOTES, 'UTF-8'));

        $returnedarray = explode(self::$carriagereturn, $referencesstring);
         
        $returnarray=array();
        //remove elements in the array not matching the format of contained references
        foreach ($returnedarray as $value){
            if($value!=''){
                $returnarray[]=$value;
            }
        }
        unset($returnedarray);

        //sort to match input?
        if(self::$sortbyinput){
            //check format - refworks xml is only support at present
            if(stripos($references,'<refworks xmlns:refworks')!==false){
                //convert string to dom
                $xml = new DOMDocument('1.0','utf-8');
                set_error_handler(array('quikbib',"HandleXmlError"));
                if(!$xml->loadXML($references)){
                    restore_error_handler();
                    return $returnarray;
                }
                restore_error_handler();
            }else{
                //return original as no support for sorting with cur format
                return $returnarray;
            }

            //get title t1 for each ref sent and put into array [Xpath]
            $xpath = new DOMXPath($xml);
            $xpath->registerNamespace ( 'refworksns' , 'www.refworks.com/xml/' );
            $nodes = $xpath->query('reference');
            $newarray=array();

            //loop through each title and find element(s) in $returnarray that match
            //if more than one? Just use first match... there is no clever way around this?
            foreach($nodes as $refnode){
                $matched=self::find_val_in_ref(&$returnarray,$refnode);
                //copy element from quikbib to new array
                if($matched!==false){
                    $newarray[]=$returnarray[$matched];
                }
            }
            //?at end compare $returnarray with $newarray if any differs then return orig!?
            if(count($newarray)==count($returnarray)){
                $returnarray=$newarray;
            }
        }


        //return the array of formatted references
        return $returnarray;
    }

    /**
     * Returns which element(s) number of haystack reference is in
     * @param $array_haystack: array to search
     * @param $needle DomNode:ref node to search for
     * @return Int, pos in haystack to use
     */
    private static function find_val_in_ref( &$array_haystack, $needle ){
        //TODO This function is a bit messy and not very comprehensive - probably needs improving + tidying

        //first check against the t1 (title tag) - assume we have this or will error
        $needle_text=$needle->getElementsByTagName('t1')->item(0)->nodeValue;
        $needle_text=html_entity_decode($needle_text, ENT_QUOTES, 'UTF-8');
        //remove whitespace (uses regex so a bit slow - but hopefully matches refworks way of removing whitespace)
        //http://uk2.php.net/manual/en/function.trim.php#72562
        $pat[0] = "/^\s+/";
        $pat[1] = "/\s{2,}/";
        $pat[2] = "/\s+\$/";
        $rep[0] = "";
        $rep[1] = " ";
        $rep[2] = "";
        $needle_text = preg_replace($pat,$rep,$needle_text);

        $ret_array=array();
        for($a=0,$max=count(&$array_haystack);$a<$max;$a++){
            if(stripos(&$array_haystack[$a],$needle_text)!==false){
                $ret_array[]=$a;
            }
        }
        $retcount=count($ret_array);
        if($retcount==1){
            //only one match so return
            return $ret_array[0];
        }else if($retcount==0){
            //oh dear no title match??
            return false;
        }
        //first, before we go thru trouble of checking more deeply, quickly check these entries are not the same anyway (duplicates)
        $matching=true;
        $test=&$array_haystack[$ret_array[0]];
        for($a=1;$a<$retcount;$a++){
            if($test != $array_haystack[$ret_array[$a]]){
                $matching=false;
                break;
            }
        }
        if($matching){
            //all the same, so just return first
            return $ret_array[0];
        }
        //check year match
        $years=$needle->getElementsByTagName('yr');
        if($years->length>0){
            $year=$years->item(0)->nodeValue;
            $needle_text=html_entity_decode($year, ENT_QUOTES, 'UTF-8');
            $new_ret_array=array();
            for($a=0,$retcount=count($ret_array);$a<$retcount;$a++){
                if(stripos(&$array_haystack[$ret_array[$a]],$needle_text)!==false){
                    $new_ret_array[]=$ret_array[$a];
                }
            }
            $retcount=count($new_ret_array);
            if($retcount==1){
                //only one match so return
                return $new_ret_array[0];
            }
        }else{
            $new_ret_array=$ret_array;
        }
        //what about edition info (along with year most probable change when match of title)
        $years=$needle->getElementsByTagName('ed');
        if($years->length>0){
            $year=$years->item(0)->nodeValue;
            if($year!=''){
                $needle_text=html_entity_decode($year, ENT_QUOTES, 'UTF-8');
                $new_ret_array2=array();
                for($a=0,$retcount=count($new_ret_array);$a<$retcount;$a++){
                    if(stripos(&$array_haystack[$new_ret_array[$a]],$needle_text)!==false){
                        $new_ret_array2[]=$new_ret_array[$a];
                    }
                }
                $retcount=count($new_ret_array2);
                if($retcount==1){
                    //only one match so return
                    return $new_ret_array2[0];
                }
                $new_ret_array=$new_ret_array2;
            }
        }
        //now check matches against the first author in ref
        $authors=$needle->getElementsByTagName('a1');
        if($authors->length>0){
            $needle_text=html_entity_decode($authors->item(0)->nodeValue, ENT_QUOTES, 'UTF-8');
            //just get surname (up to comma) as first name(s) may be turned to initials
            $needle_text=explode(',',$needle_text,2);
            $new_ret_array2=array();
            for($a=0,$retcount=count($new_ret_array);$a<$retcount;$a++){
                if(stripos(&$array_haystack[$new_ret_array[$a]],$needle_text[0])!==false){
                    $new_ret_array2[]=$new_ret_array[$a];
                }
            }
            $retcount=count($new_ret_array2);
            if($retcount==1){
                //only one match so return
                return $new_ret_array2[0];
            }
            $new_ret_array=$new_ret_array2;
        }
        //so far have matched title + primary author - now check against?
        if($authors->length>1){
            //secondary author (do not check every author as some formats only use a couple then add 'et al')
            $needle_text=html_entity_decode($authors->item(1)->nodeValue, ENT_QUOTES, 'UTF-8');
            //just get surname (up to comma) as first name(s) may be turned to initials
            $needle_text=explode(',',$needle_text,2);
            $new_ret_array2=array();
            for($a=0,$retcount=count($new_ret_array);$a<$retcount;$a++){
                if(stripos(&$array_haystack[$new_ret_array[$a]],$needle_text[0])!==false){
                    $new_ret_array2[]=$new_ret_array[$a];
                }
            }
            $retcount=count($new_ret_array2);
            if($retcount==1){
                //only one match so return
                return $new_ret_array2[0];
            }
            $new_ret_array=$new_ret_array2;
        }
        //try start page (might be same article, dif page)
        //only prob here if same sp is used but dif end page or end page elsewhere is same as this sp
        //or of course, incidental number matches sp (highly unlikely)
        $sp=$needle->getElementsByTagName('sp');
        if($sp->length>0){
            $sptext=$sp->item(0)->nodeValue;
            if($sptext!=''){
                $needle_text=html_entity_decode($sptext, ENT_QUOTES, 'UTF-8');
                $new_ret_array2=array();
                for($a=0,$retcount=count($new_ret_array);$a<$retcount;$a++){
                    if(stripos(&$array_haystack[$new_ret_array[$a]],$needle_text)!==false){
                        $new_ret_array2[]=$new_ret_array[$a];
                    }
                }
                $retcount=count($new_ret_array2);
                if($retcount==1){
                    //only one match so return
                    return $new_ret_array2[0];
                }
                if($retcount>0){
                    //sometimes page numbers aren't in title anyway
                    $new_ret_array=$new_ret_array2;
                }

            }
        }
        //no matches
        if(count($new_ret_array)>0){
            //return 1st match anyway (better than fail!)
            return $new_ret_array[0];
        }
        return false;
    }

    //function to handle error when transforming xml string to DOM
    static function HandleXmlError($errno, $errstr, $errfile, $errline){
        if ($errno==E_WARNING && (substr_count($errstr,"DOMDocument::loadXML()")>0))
        {
            self::$lasterror=$errstr;
        }else{
            return false;
        }
    }
}

?>